/* 
 * Copyright (C) 2012 Mark Thompson (mark.artifice@gmail.com)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.artificetech.videolibrary.metadata;

import java.util.Date;
import java.util.List;
import java.util.Vector;

import com.artificetech.videolibrary.metadata.VideoData.ArtProvider;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.provider.MediaStore;
import android.util.Log;

/**
 * A class managing the querying for videos on an Android system.
 */
public class MediaQuery {
	private static final String TAG="MediaQuery";

	/** The interface that metadata-parsers implement. */
	public interface VideoDataProvider {
		/**
		 * @return true, if this decoder would like to attempt a file with the stated mime-type. 
		 */
		public boolean handlesType(String mimeType); 
		/**
		 * Attempt to update the metadata provided. 
		 * A reference to the source file is available as v.path.
		 * 
		 * v will be filled with at least that information available though the Android MediaScanner;
		 * other providers may also have filled in some information.
		 * 
		 * @return true, if at least some data has been updated.
		 */
		public boolean updateInfo(VideoData v);
	}
	
	/** An asynchronous callback that monitors a media scan. */ 
	public interface VideoDataListener {
		/** Called when a new video has been added, and its metadata loaded. */
		void videoAdded(VideoData v);
		/** Called on a best-effort basis to notify the caller of scan progress. Progress is a fraction 0..1. You should not rely on this being called. */
		void setProgress(float progress);
		/** Called when the scan completes; at about the same time setProgress(1.0f) would be called. */
		void scanComplete();
	}
	
	/** The creating activity; used to manage database accesses. */
	private final Activity owningActivity;
	
	/** The index of metadata parsers available. */
	private final List<VideoDataProvider> dataProviders;

	/**
	 * An implementation of VideoData.ArtProvider that extracts a thumbnail from MediaScanner's database.
	 * 
	 * For videos, Android seems to like generating a frame thumbnail, so that's not ideal.
	 */
	private static class AndroidThumbnailProvider implements ArtProvider {
		/** The Thumbnails service needs one of these; obtain it from an Activity. */
		private ContentResolver contentResolver;
		/** The ID of the video we're generating a thumbnail for. */
		long id;
		
		public AndroidThumbnailProvider(ContentResolver _contentResolver, long _id) {
			contentResolver=_contentResolver;
			id=_id;
		}

		public Bitmap get() {
			// Don't care what sort of image we get.
			BitmapFactory.Options options=new BitmapFactory.Options();
			return MediaStore.Video.Thumbnails.getThumbnail(contentResolver, id, MediaStore.Video.Thumbnails.MINI_KIND, options); 
		}
	}
	
	public MediaQuery(Activity _owningActivity) {
		owningActivity=_owningActivity;
		dataProviders=new Vector<VideoDataProvider>();
		dataProviders.add(new MP4VideoDataProvider(owningActivity));
	}
	
	/**
	 * Query for videos and attempt to parse metadata, returning results via the listener interface.
	 * Completes synchronously.
	 * 
	 * @param owningActivity One of these is required for the MediaScanner and Thumbnail queries.
	 * @param listener The callback to recieve video data.
	 */
	public void getVideos(VideoDataListener listener) {
		//Query the media store for the available videos.
		String[] proj = { 
			MediaStore.Video.Media._ID, 
			MediaStore.Video.Media.DATA,
			MediaStore.Video.Media.MIME_TYPE,
			MediaStore.Video.Media.TITLE,
			MediaStore.Video.Media.DURATION,
			MediaStore.Video.Media.DATE_TAKEN,
			MediaStore.Video.Media.DESCRIPTION,
			MediaStore.Video.Media.CATEGORY,
			MediaStore.Video.Media.ARTIST
		};
		
		@SuppressWarnings("deprecation")
		Cursor cursor = owningActivity.managedQuery(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, proj, null, null, null);

		// For each video in turn,
		if (!cursor.moveToFirst()) return;
		
		int count=cursor.getCount();
		
		while (!cursor.isAfterLast()) {
			long id=Long.parseLong(cursor.getString(0));
			
			String path=cursor.getString(1);
			String mimeType=cursor.getString(2);
			
			try {
				// Create a new video data
				VideoData v=new VideoData(ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id), mimeType, path);
				// Start by filling in as much data as possible from the Android database.
				// These mappings aren't very exact for movies.
				v.title=cursor.getString(3);
				v.duration=cursor.getInt(4);
				v.releaseDate=new Date(cursor.getLong(5));
				v.description=cursor.getString(6);
				v.genre=cursor.getString(7);
				v.cast=null;
				v.director=cursor.getString(8);
				v.coverArt=new AndroidThumbnailProvider(owningActivity.getContentResolver(), id);
					
				// Now, do we have a provider that claims to be able to do better?
				for (VideoDataProvider provider : dataProviders) {
					if (!provider.handlesType(mimeType)) continue;
					
					provider.updateInfo(v);
				}
				
				// Update the sort field, if one wasn't provided;
				if (v.titleSort==null) v.titleSort=generateSortName(v.title);
				
				// Let the caller know about this video.
				listener.videoAdded(v);
			}
			catch (Exception e) {
				// Can't parse this video file.
				Log.i(TAG, "Failed to parse video file: "+path, e);
				// Ignore it.
			}

			cursor.moveToNext();
			
			float progress=(float)cursor.getPosition() / count;
			listener.setProgress(progress);
		}
		
		listener.scanComplete();
	}
	
	/** Leading articles that will be stripped from a title to generate a sort name. These must be in lowercase. */
	private static final String[] STRIP_ARTICLES={"a ", "an ", "the "};
	
	private String generateSortName(String title) {
		// Convert to lowercase
		title=title.toLowerCase();
		
		boolean changed=true;
		while (changed) {
			changed=false;
			// Strip leading articles
			for (String article : STRIP_ARTICLES) {
				if (title.startsWith(article)) {
					title=title.substring(article.length());
					changed=true;
				}
			}
			// Strip leading non-alphabetic, non-numerical characters
			if (!Character.isLetterOrDigit(title.charAt(0))) {
				title=title.substring(1);
				changed=true;
			}
		}
		
		return title;
	}

	/** A wrapper around getVideos that spins off a new thread for the query. */
	public Thread asyncGetVideos(final VideoDataListener listener) {
		Runnable r=new Runnable() {
			public void run() {
				getVideos(listener);
			}
		};
		
		Thread t=new Thread(r);
		t.start();
		return t;
	}
	
	// Helper functions that create a MediaQuery implicitly.
	public static void getVideos(Activity owningActivity, VideoDataListener listener) {
		MediaQuery mq=new MediaQuery(owningActivity);
		mq.getVideos(listener);
	}
	
	public static Thread asyncGetVideos(Activity owningActivity, VideoDataListener listener) {
		MediaQuery mq=new MediaQuery(owningActivity);
		return mq.asyncGetVideos(listener);
	}
}
